Chapter 6

章前占位。- -!

6.1 使用一等函数实现设计模式

作为一等对象的函数称为 一等函数

6.1.1 案例分析:重构 策略 模式

图 6-1 中的 UML 类图指出了 策略 模式对类的编排。

6-1.png|图 5-1 使用 策略 设计模式处理订单折扣的 UML 类图

上图中的上下文指的是调用 Order 类的其他代码。

电商中根据客户的属性或订单中的商品计算折扣功能明显可以使用 策略 模式。

案例:

假如一个网店制定了下述折扣规则:

分析:

上下文。案例中与上下文有联系的是订单类,即 Order

策略。使用名为 Promotion 的抽象类为实现不同策略提供共同接口

具体策略。策略类(即 Promotion 类)的具体子类的。fidelityPromoBulkPromoLargeOrderPromo 三个类实现具体策略。

代码:

from abc import ABC, abstractmethod  
from collections import namedtuple  
  
Customer = namedtuple("Customer", "name fidelity")  
  
  
class LineItem:  
	"""
	商品信息类。
	属性:商品名 product,数量 quantity,单价 price
	方法:计算总价格 total()
	"""
    def __init__(self, product, quantity, price):  
        self.product = product  
        self.quantity = quantity  
        self.price = price  
  
    def total(self):  
        return self.quantity * self.price  
  
  
class Order:  # 订单
	"""
	订单类。
	属性:客户/消费者 customer, 购物车 cart, 折扣策略 promotion
	方法:没有折扣的总价格 total(), 根据不同策略打折后的总价格 due()
		 重写 __repr__(), 用于显示调 Order 类时的显示内容
	"""
    def __init__(self, customer, cart, promotion=None):  
        self.customer = customer  
        self.cart = list(cart)  
        self.promotion = promotion  
  
    def total(self):  
        if not hasattr(self, "__total"):  
            self.__total = sum(item.total() for item in self.cart)  
        return self.__total  
  
    def due(self):  
        if self.promotion is None:  
            discount = 0  
        else:  
            discount = self.promotion.discount(self)  
  
        return self.total() - discount  
  
    def __repr__(self):  
        fmt = "<Order total: {:.2f} due: {:.2f}>"  
        return fmt.format(self.total(), self.due())  
  
  
class Promotion(ABC):  # 策略:抽象基类  
    @abstractmethod  
    def discount(self, order):  
        """返回折扣金额(正值)"""  
  
  
class FidelityPromo(Promotion):  # 具体策略一  
    """为积分为 1000 或以上的顾客提供 5% 折扣"""  
    def discount(self, order):  
        return order.total() * .05 if order.customer.fidelity >= 1000 else 0  
  
  
class BulkItemPromo(Promotion):  # 具体策略二  
    """单个商品为 20 个或以上时提供 10% 折扣"""  
    def discount(self, order):  
        discount = 0  
        for item in order.cart:  
            if item.quantity >= 20:  
                discount += item.total() * .1  
        return discount  
  
  
class LargeOrderPromo(Promotion):  # 具体策略三  
    """订单中的不同商品达到 10 个或以上时提供 7% 折扣"""  
    def discount(self, order):  
        distinct_item = {item.product for item in order.cart}  # 可以去重?  
        if len(distinct_item) >= 10:  
            return order.total() * .07  
        return 0

上述代码可以正常运行。

测试代码:

    >>> joe = Customer('John Doe', 0) 
    >>> ann = Customer('Ann Smith', 1100)
    >>> cart = [LineItem('banana', 4, .5), 
    ...         LineItem('apple', 10, 1.5),
    ...         LineItem('watermellon', 5, 5.0)]
    >>> Order(joe, cart, FidelityPromo()) 
    <Order total: 42.00 due: 42.00>
    >>> Order(ann, cart, FidelityPromo()) 
    <Order total: 42.00 due: 39.90>
    >>> banana_cart = [LineItem('banana', 30, .5), 
    ...                LineItem('apple', 10, 1.5)]
    >>> Order(joe, banana_cart, BulkItemPromo()) 
    <Order total: 30.00 due: 28.50>
    >>> long_order = [LineItem(str(item_code), 1, 1.0) 
    ...               for item_code in range(10)]
    >>> Order(joe, long_order, LargeOrderPromo()) 
    <Order total: 10.00 due: 9.30>
    >>> Order(joe, cart, LargeOrderPromo())
    <Order total: 42.00 due: 42.00>

6.1.2 使用函数实现“策略”模式

前例是使用类实现每个具体策略,但是会发现:

代码:

from collections import namedtuple

Customer = namedtuple("Customer", "name fidelity")

class LineItem:
	def __init__(self, product, quantity, price):
		self.product = product
		self.quantity = quantity
		self.price = price
	def total(self):
		return self.quantity * self.price

class Order:
	def __init__(self, customer, cart, promotion=None)
		self.customer = customer
		self.cart = list(cart)
		self.promotion = promotion
	def total(self):
		if not hasattr(self, "__total"):
			self.__total = sum(item.total() for item in self.cart)
		return self.__total
	def due(self)
		if self.promotion is None:
			discount = 0
		else:
			discount = self.promotion(self)
	def __repr__(self):
		msg = "<Order total: {:.2f}, due: {:.2f}>"
		return msg.format(self.total(), self.due())

def fidelity_promot(order):
	"""为积分为 1000 或以上的顾客提供 5% 折扣"""
	return order.total() * .05 if order.customer.fidelity >= 1000 else 0

def bulk_item_promo(order):
	"""单个商品为 20 个或以上时提供 10% 折扣"""
	discount = 0
	for iterm in order.cart:
		if iterm.quantity >= 20:
			discount += item.total * .1
	return discount

def large_order_promo(order):
	"""订单中的不同商品达到 10 个或以上时提供 7% 折扣"""
	discount_items = {item.product for item in order.cart}
	if len(distinct_items) >= 10:
		return order.total() * .07
	return 0

测试代码:

>>> joe = Customer('John Doe', 0)  
>>> ann = Customer('Ann Smith', 1100)
>>> cart = [LineItem('banana', 4, .5),
...         LineItem('apple', 10, 1.5),
...         LineItem('watermellon', 5, 5.0)]
>>> Order(joe, cart, fidelity_promo)  # 1
<Order total: 42.00 due: 42.00>
>>> Order(ann, cart, fidelity_promo)
<Order total: 42.00 due: 39.90>
>>> banana_cart = [LineItem('banana', 30, .5),
...                LineItem('apple', 10, 1.5)]
>>> Order(joe, banana_cart, bulk_item_promo)  # 2
<Order total: 30.00 due: 28.50>
>>> long_order = [LineItem(str(item_code), 1, 1.0)
...               for item_code in range(10)]
>>> Order(joe, long_order, large_order_promo) # 3
<Order total: 10.00 due: 9.30>
>>> Order(joe, cart, large_order_promo)
<Order total: 42.00 due: 42.00>

上面测试代码中 # 1# 2# 3 处与使用策略类实现的测试代码中相同位置代码对比少了括号,具体对比:

Order(joe, cart, fidelity_promo)  # 函数实现策略
Order(joe, cart, FidelityPromo())  # 类实现策略

6.1.3 选择最佳策略:简单方式

为顾客找到折扣额度最大的策略

promos = [fidelity_promo, bulk_item_promo, larg_order_promo]
def best_promo(order):
	"""选择可用的最佳折扣"""
	return max(promo(order) for promo in promos)

测试代码:

>>> Order(joe, long_order, best_promo)  
<Order total: 10.00 due: 9.30>
>>> Order(joe, banana_cart, best_promo)  
<Order total: 30.00 due: 28.50>
>>> Order(ann, cart, best_promo)  
<Order total: 42.00 due: 39.90>

6.1.4 找出模块中的全部策略

Pytrhon 中,模块也是一等对象,标准库中提供了几个处理模块的函数。

1、globals()

返回一个 字典,表示当前的全局符号表。这个符号表始终针对当前模块(对函数或方法来说,是指定义它们的模块,而不是调用它们的模块)。

示例:使用 globals 函数帮助 best_promo 自动找到其他可用的 *_promo 函数。

# 内省模块的全局命名空间,构建 promos 列表
promos = [globals() [name] for name in globals() if name.endwith("_promo") and name != "best_promo"]

def best_promo(order):
	"""
	选择可用的最佳折扣"
	"""
	return max(promo(order) for promo in promos)

6.2 “命令” 模式

“命令” 设计模式也可以通过把函数作为参数传递而简化。这一模式对类的编排如图 6-2 所示。

6-2.png|图 6-2:菜单驱动的文本编辑器的 UML 类图,使用 “命令” 设计模实现。各个命令可以有不同的接收者(实现操作的对象)。对 PasteCommand 来说,接收者是 Document。对于 OpenCommand 来说,接收者是应用程序

小葵花妈妈课堂,开讲了